#include "Consumer.h"
#include "EcoSystem.h"
#include "FoodWeb.h"
#include "Environment.h"
#include <cmath>

Consumer::Consumer(EcoSystem *eco_system) :
Entity(eco_system) {}


bool Consumer::is_producer() const
{
    return false;
}

bool Consumer::is_consumer() const
{
    return true;
}



double Consumer::get_speed() const
{
    return speed;
}

void Consumer::set_speed(double speed)
{
    this->speed = speed;
}

double Consumer::get_init_speed() const
{
    return init_speed;
}

void Consumer::set_init_speed(double init_speed)
{
    this->init_speed = init_speed;
}

/*
 *Predation.Parameter speed is the speed of this animal while it's chasing its prey.
 *Returns true if this animal found a prey.
 */
bool Consumer::prey(double speed)
{
    set_speed(speed);
    Entity *food = eco_system->find_prey(this);
    if (food != NULL)
    {
        //Wooooooooooow!!!I see my delicious dinner near me!!!
        if (!eco_system->try_eat(this, food))
        {
            /*
             *Suppose the position of the prey is target_position.
             *The direction that this animal should run toward is the direction of [(target_position) -(position of this animal)]
             *Normalize the vector [(target_position) -(position of this animal)] then times the speed we get displacement
             *{(position of this animal) + displacement} is the next position of this animal 
             */
            Vector2D target_position = food->get_position();
            Vector2D distance = target_position - this->get_position();
            Vector2D displacement = distance * (1.0 / distance.modulus()) * get_speed();
            target_position = get_position() + displacement;
            this->set_position(target_position);
        }
        return true;
    }
    return false;//404 Prey not found.
}

bool Consumer::random_motion(double speed)
{
    if (is_near_edge())
        return brownian_motion(speed);
    set_speed(speed);
    Vector2D displacement = get_position() - get_prev_position();
    int rotate_direction = (rand() % 2) & 1 ? 1 : -1;
    double rotate_angle = EcoSystem::random_angle() / (360.0 / MAX_RANDOM_TURN_ANGLE);
    displacement = displacement.rotate(rotate_direction * rotate_angle);
    if (displacement.modulus() < 1.0)
        return brownian_motion(speed);
    else
        displacement = displacement * (speed / displacement.modulus());
    set_position(get_position() + displacement);
    return true;
}

/*
 *Walk toward a random direction with a specified speed
 */
bool Consumer::brownian_motion(double speed)
{
    set_speed(speed);
    /*
     *First generate a unit vector.
     *Rotate it by a random angle.
     *Times the speed of this animal.
     *Then we get displacement.
     *[(position of this animal) + displacement] is the next position of this animal .
     */
    Vector2D unit_vector(1.0, 0.0);
    unit_vector = unit_vector.rotate(EcoSystem::random_angle());
    Vector2D displacement = unit_vector * get_speed();
    Vector2D target_position = get_position() + displacement;
    EcoSystem::prevent_overstep(target_position);
    set_position(target_position);
    return true;
}

/*
 *Behavior of avoiding the predator of this animal.
 *It's vital for an animal to avoid its predators.
 *Otherwise it will die.
 *Returns true if this animal realize there are predators around it.
 */
bool Consumer::avoid_predator(double speed)
{
    /*
     *If it found predators around it, it would run toward one direction for 5 ticks.
     */
    if (aps.t <= 10)
    {
        ++aps.t;
        if (is_near_edge())
            aps.v = aps.v.rotate(EcoSystem::random_angle());
        set_position(get_position() + aps.v);
        return true;
    }
    int predators_count = eco_system->get_around_predators_count(this);
    if (predators_count != 0)
    {
        /*
         *If there are predators around it!!
         *Suppose there are n predators around and the position vector of the i-th predator is pos_i
         *The direction of this animal to run away is the direction of
         *
         *       n
         * v = sigma (position of this animal - pos_i)
         *      i=1
         *
         *Simplify it, then 
         *
         *                                      n
         *v = n * (position of this animal) - sigma (pos_i)
         *                                     i=1
         *
         *and the term [sigma (pos_i)] has been calculated by EcoSystem
         */
        aps.t = 0;
        set_speed(speed);
        Vector2D predator_pos_sum = eco_system->get_around_predators_position_sum(this);
        Vector2D d = predators_count * get_position() - predator_pos_sum;


        if (d.modulus() < 1.0)//Avoid divided-by zero exception
            d = Vector2D(1, 0).rotate(EcoSystem::random_angle()) * get_speed();
        else
            d = d * (get_speed() / d.modulus());
        set_position(get_position() + d);
        aps.v = d;
        set_target(NULL);
        return true;
    }
    return false;
}

/*
 *Multiply.Generate a baby of this animal.
 */
void Consumer::multiply()
{
    int age = get_age(), life_span = get_life_span();
    if ( age > life_span/ 2 || age < life_span/4)
        return;

    
    double p = 1.0 / 104 * 50000 / get_max_hp();
    if (hungry()> 0.70 && EcoSystem::random_double() < p)
    {
        Vector2D bias(10, 10);
        eco_system->spawn_entity(new_entity(), get_position() + bias);
        set_hp(get_hp()*0.65);
    }
}

bool Consumer::is_near_edge() const
{
    Vector2D &pos = get_position();
    return (pos.x < 5.0 || pos.y < 5.0 || pos.x > EcoSystem::DEFAULT_WIDTH - 5.0 || pos.y > EcoSystem::DEFAULT_HEIGHT - 5.0);
}
/*
 *Some animals would take some actions when they are about to be eaten.
 *For example, scream to warn other animals around it.
 */
void Consumer::on_eaten()
{}

void Consumer::set_life_span(int span)
{
    life_span = span;
}
int Consumer::get_life_span() const
{
    return life_span;
}

double Consumer::get_speed_limit() const
{
    return MAX_SPEED[get_species_id()];
}

int Consumer::get_hp_limit() const
{
    return MAX_HP[get_species_id()];
}

double sigmoid(double x)
{
    double tmp = exp(-x);
    return tmp / (1 + tmp) / (1 + tmp);
}
void Consumer::natural_growth() 
{ 
    int age = get_age();
    int life_span = get_life_span();

    if (age < life_span / 4)
    {
        double status = sqrt(get_hp() / get_max_hp())*10;
        double rate = sigmoid((double)5 * get_age() / life_span) / 3;
        if (age % 10== 0)
        {
            set_max_hp(get_max_hp()*(1 + status*rate));
            set_hp(get_hp()*(1 + status*rate-0.05));
            set_init_speed(get_init_speed()*(1 + status*rate));
        }
    }
    else if (age>0.65*life_span)
    {
        double rate = sigmoid((double)5 *( get_age() -2*life_span/3)/ life_span) / 3;
        set_max_hp(max(get_max_hp()*(1 - rate), 5000));
        set_init_speed(max(get_init_speed()*(1 -rate),2));
    }

    set_max_hp(min(get_max_hp(), get_hp_limit()));
    set_hp(min(get_hp(), get_max_hp()));
    set_init_speed(min(get_init_speed(), get_speed_limit()));

    if (age >= life_span)
        set_dead(); 
}

double Consumer::get_environment_factor()
{
    double factor = 1.0;
    Environment *ev_ptr = eco_system->get_environment_instance();

    double temperature = ev_ptr->get_temperature();
    if (temperature > 30)
        factor /= temperature / 30;
    else if (temperature < 15)
        factor *= sqrt(temperature / 15*100)/12;

    double brightness = ev_ptr->get_brightness();
    if (brightness < 0.65)
    factor *= sqrt(ev_ptr->get_brightness()*100) /12;

    return max(factor,0.15);
}


double Consumer::hungry()
{
    return get_hp() / get_max_hp();
}
